from urllib import parse

from django.db import models
from django.urls import reverse, reverse_lazy
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.utils.translation import gettext_lazy as _
from django.utils.safestring import mark_safe
from django.utils import timezone
from cuser.middleware import CuserMiddleware

from django_countries.fields import CountryField

from app.common import truncatechars, get_client_ip
from .utils import generate_random_slug, get_ip_info, get_device_type, get_referer, \
    get_domain_ip, get_valid_ips
from .validators import DomainARecoredValidator


# this is a funtion.
User = get_user_model()
get_user = CuserMiddleware.get_user


class Link(models.Model):

    class STATUS(models.IntegerChoices):
        ACTIVE = (0, _('active'))
        INACTIVE = (1, _('inactive'))

    class TYPES(models.IntegerChoices):
        DIRECT = 0, _('Direct')

    title = models.CharField(max_length=256)
    slug = models.SlugField(max_length=40, unique=True, blank=True, help_text=_(
        'Set a custom slug or leave it emply to generate a random one for you, automatically!'))
    status = models.IntegerField(
        choices=STATUS.choices, default=STATUS.ACTIVE, db_index=True)
    original_url = models.URLField(verbose_name=_(
        'Original URL'), help_text=_('A link to redirect to.'))

    user = models.ForeignKey(User, related_name='links',
                             on_delete=models.CASCADE, default=get_user, blank=True)
    domain = models.ForeignKey(
        'Domain', related_name='links', on_delete=models.CASCADE, db_index=True)

    link_type = models.IntegerField(
        choices=TYPES.choices, default=TYPES.DIRECT, db_index=True)

    start_date = models.DateTimeField(_('Start Date'), null=True, blank=True, help_text=_(
        '<b>Enter a date where the link should be active.</b>'))
    end_date = models.DateTimeField(_('End Date'), null=True, blank=True)

    total_clicks = models.IntegerField(
        _('Total clicks'), default=0, blank=True)
    max_clicks = models.IntegerField(
        _('Maximum Clicks'), default=0, help_text=_("The link won't be accessable after reaching the maximum clicks amount or redirect to a different URL.<br><b>(0 value means unlimited clicks!)</b>."))
    redirect_url = models.URLField(_('Redirect to'), null=True, blank=True, help_text=_(
        'Redirect to this URL after reaching the maximum clicks.'))

    password = models.CharField(max_length=64, null=True, blank=True, help_text=_(
        'A password to restrict access to your link.'))

    updated = models.DateTimeField(auto_now=True, db_index=True)
    created = models.DateTimeField(auto_now_add=True, db_index=True)

    class Meta:
        ordering = ('-created',)

    def icon(self):
        return "<i class='fa fa-link nav-icon'></i>"

    def is_active(self):
        return self.status == self.STATUS.ACTIVE

    def is_expired(self):
        if self.start_date and self.end_date:
            return not (self.start_date < timezone.now() < self.end_date)
        elif self.end_date:
            return not (timezone.now() < self.end_date)
        elif self.start_date:
            return not (self.start_date < timezone.now())
        return False

    def is_max_clicks_reached(self):
        return self.total_clicks > self.max_clicks if self.max_clicks else False

    def is_valid(self):
        """check for expiration & max clicks."""
        return all([not self.is_expired(), not self.is_max_clicks_reached(), self.is_active()])

    def get_destination_link(self):
        if self.is_max_clicks_reached() and self.redirect_url:
            return self.redirect_url
        return self.append_utm(self.original_url)

    def append_utm(self, url):
        if hasattr(self, 'UTM'):
            new_query = self.UTM.build_query()
            parsed = parse.urlparse(url)
            query = dict(parse.parse_qsl(parsed.query))
            query.update(new_query)
            parsed = parsed._replace(query=parse.urlencode(query))
            url = parsed.geturl()
        return url

    def new_url(self):
        return f'{self.domain}/{self.slug}'

    @admin.display(description=_('clicks'))
    def clicks(self):
        # from database: self.stats.count()
        return self.total_clicks

    def clean(self, *args, **kwargs):
        if not self.slug:
            self.slug = generate_random_slug()
        return super().clean(*args, **kwargs)

    @admin.display(description=_('Status'))
    def get_status(self):
        class_name = 'badge-success' if self.status == self.STATUS.ACTIVE else 'badge-warning'
        return mark_safe(f"<span class='bp'><span class='badge badge-pill {class_name} text-xs'>{self.get_status_display()}</span></span>")

    @admin.display(description=_('Summary'))
    def show_urls(self):
        return mark_safe(f"""
            <a href="{self.original_url}" target="_blank" title="{self.original_url}">
                <img src='https://www.google.com/s2/favicons?domain={self.original_url}'>
                <b>{truncatechars(self.original_url, 50)}</b>
            </a><br>
            <a href="{self.new_url()}" target="_blank" title="{self.new_url()}">
                {truncatechars(self.new_url(), 50)}
            </a>
        """)

    @admin.display(description=_('Summary'))
    def show_urls_with_title(self):
        title = f"""<a href="{reverse_lazy('admin:links_manager_link_stats', args=(self.id,))}"><b>{self.title}</b></a>"""
        return mark_safe("%s<br>%s" % (title, self.show_urls()))

    @admin.display(description=_('actions'))
    def show_actions(self):
        return mark_safe(f"""
            <div class='bp' style='width: 112px'>
                <a href='{reverse_lazy('admin:links_manager_link_stats', args=(self.id,))}' class='btn btn-success btn-xs text-white'><i class='fa fa-chart-simple fa-fw'></i> Stats</a>

                <a href='{reverse_lazy('admin:links_manager_link_change', args=(self.id,))}' class='btn btn-primary btn-xs text-white'><i class='fa fa-pen fa-fw'></i> Edit</a>
            </div>
        """)

    def __str__(self):
        return self.title

    def get_valid_targeting(self, *args, **kwargs):
        # return the first in the valid targeting list.
        valid_list = self.get_all_valid_targeting(*args, **kwargs)
        # return the first Targeting
        if valid_list:
            return valid_list[0][1]
        return None

    def get_all_valid_targeting(self, country=None, device_type=None, platform=None):
        valid_list = []
        for target in self.targeting.all():
            if target.is_valid(country, device_type, platform):
                priority = target.priority()
                valid_list.append((priority, target))
        # Sorting by priority
        valid_list.sort()
        return valid_list

    def register_stats(self, req, user_agent):
        user = self.user
        referer = get_referer(req)
        print('referer: ', referer)

        client_ip = get_client_ip(req)
        ip_info = get_ip_info(client_ip)

        country = ip_info.get('country', None)
        city = ip_info.get('city', None)
        region = ip_info.get('region', None)
        device_type = get_device_type(user_agent)
        platform = user_agent.os.family

        if user_agent and not user_agent.is_bot and self.is_valid():
            # Update link data.
            self.total_clicks += 1
            self.save()

            # Save a new Stat
            stat = Stat(user=user, link=self)
            stat.ip = client_ip
            stat.country = country
            stat.city = city
            stat.region = region
            stat.browser = user_agent.browser.family
            stat.os = platform
            stat.device = user_agent.device.family
            stat.device_type = device_type
            stat.referer = referer

            stat.save()

        return country, device_type, platform

    def is_able_to_redirect(self):
        if self.is_active() and not self.is_expired():
            if not (self.is_max_clicks_reached() and not self.redirect_url):
                return True
        return False

    def get_final_url(self, country, device_type, platform):
        # Checking country, platform, device

        targeting = self.get_valid_targeting(
            country=country,
            device_type=device_type,
            platform=platform
        )

        # Redirect if a targeting is matched
        if targeting and targeting.redirect_url:
            return self.append_utm(targeting.redirect_url)

        # Default redirection.
        return self.get_destination_link()


class Targeting(models.Model):
    class DEVICE_TYPE(models.IntegerChoices):
        ANY = 3, _('(any device)')
        DESKTOP = 0, _('Desktop')
        MOBILE = 1, _('Mobile')
        TABLET = 2, _('Tablet')

    class PLATFORM(models.IntegerChoices):
        ANY = 0, _('(any platform)')
        WINDOWS = 1, _('Windows')
        LINUX = 2, _('Linux')
        ANDROID = 3, _('Android')
        MACOS = 4, _('Mac OS X')
        IOS = 5, _('iOS')

    link = models.ForeignKey(
        'Link', on_delete=models.CASCADE, related_name='targeting')
    country = CountryField(blank_label=_('(any country)'), db_index=True)
    device_type = models.IntegerField(
        choices=DEVICE_TYPE.choices, default=DEVICE_TYPE.ANY, db_index=True)
    platform = models.IntegerField(
        choices=PLATFORM.choices, default=PLATFORM.ANY, db_index=True)
    redirect_url = models.URLField(max_length=512)

    def __str__(self):
        return 'Targeting (%d)' % self.id

    def is_valid(self, country=None, device_type=None, platform=None):
        # print(f'======== {str(self)} ========')
        # print('is_valid_country: ', self.is_valid_country(country))
        # print('is_valid_device: ', self.is_valid_device(device_type))
        # print('is_valid_platform: ', self.is_valid_platform(platform))
        return all((self.is_valid_country(country), self.is_valid_device(device_type), self.is_valid_platform(platform)))

    def is_valid_country(self, country=None):
        return any((self.country is None, self.country.code.lower() == str(country).lower()))

    def is_valid_device(self, device_type=None):
        return any((self.device_type == self.DEVICE_TYPE.ANY, self.device_type == device_type))

    def is_valid_platform(self, platform=None):
        return any((
            self.platform == self.PLATFORM.ANY,
            self.get_platform_display().lower() == str(platform).lower()
        ))

    def priority(self):
        priority = 1
        if self.country is None:
            priority += 1
        if self.device_type == self.DEVICE_TYPE.ANY:
            priority += 1
        if self.platform == self.PLATFORM.ANY:
            priority += 1

        return priority


class UTM(models.Model):
    link = models.OneToOneField(
        'Link', on_delete=models.CASCADE, related_name='UTM')
    campaign_source = models.CharField(max_length=256, help_text=_(
        'e.g. newsletter, twitter, google, etc.'))
    campaign_medium = models.CharField(
        max_length=256, help_text=_('e.g. email, social, cpc, etc.'))
    campaign_name = models.CharField(
        max_length=256, help_text=_('e.g. promotion, sale, etc.'))

    campaign_id = models.CharField(
        _('Campaign ID'), max_length=256, null=True, blank=True, help_text=_('The ads campaign id.'))
    campaign_term = models.CharField(max_length=256, null=True, blank=True, help_text=_(
        'Keywords for your paid search campaigns'))
    campaign_content = models.CharField(max_length=256, null=True, blank=True, help_text=_(
        'Any call-to-action or headline, Use to differentiate ads, e.g. buy-now.'))

    def build_query(self, encode_it=False):
        query = {}

        query.update({
            'utm_source': self.campaign_source,
            'utm_medium': self.campaign_medium,
            'utm_campaign': self.campaign_name
        })

        if self.campaign_id:
            query['utm_id'] = self.campaign_id
        if self.campaign_term:
            query['utm_term'] = self.campaign_term
        if self.campaign_content:
            query['utm_content'] = self.campaign_content

        if encode_it:
            return parse.urlencode(query)
        return query

    def __str__(self):
        return 'UTM (%d)' % self.id


class Domain(models.Model):

    class STATUS(models.IntegerChoices):
        ACTIVE = (0, _('active'))
        INACTIVE = (1, _('inactive'))

    url = models.URLField(verbose_name=_('Domain'), validators=[DomainARecoredValidator], help_text=_(
        'e.g. http://example.com, https://sub.example.com'))
    user = models.ForeignKey(
        User, related_name='domains', on_delete=models.CASCADE, default=get_user, blank=True)
    status = models.IntegerField(choices=STATUS.choices, default=STATUS.ACTIVE)

    default = models.BooleanField(_('Default domain'),
                                  default=False, blank=True, help_text=_('Allow other users to use it.'))

    updated = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('-created',)

    def icon(self):
        return "<i class='fa fa-globe nav-icon'></i>"

    @admin.display(description=_('Status'))
    def get_status(self):
        class_name = 'badge-success' if self.status == self.STATUS.ACTIVE else 'badge-secondary'
        return mark_safe(f"<span class='bp'><span class='badge badge-pill {class_name} text-xs'>{self.get_status_display()}</span></span>")

    @admin.display(description=_('Connected'))
    def is_connected(self):
        if self.ready():
            class_name = 'text-success'
            icon = '<i class="bi bi-hdd-network-fill"></i>'
            name = _('Connected')
        else:
            class_name = 'text-danger'
            icon = '<i class="bi bi-hdd-network"></i>'
            name = _('Not connected')

        return mark_safe(f"<span class='bp'><span class='h5 {class_name}' title='{name}'>{icon}</span></span>")

    def __str__(self):
        return self.url

    def ready(self):
        """Check if this domain has a correct DNS Record (A Record)."""
        ip = get_domain_ip(self.url)
        if ip in get_valid_ips():
            return True
        return False


class Stat(models.Model):

    class DEVICE_TYPE(models.IntegerChoices):
        DESKTOP = 0, _('Desktop')
        MOBILE = 1, _('Mobile')
        TABLET = 2, _('Tablet')
        UNKNOWN = 3, _('Unknown')

    ip = models.GenericIPAddressField(verbose_name=_('IP'))
    country = CountryField(blank_label=_('(select country)'), db_index=True)
    city = models.CharField(max_length=40, blank=True,
                            null=True, db_index=True)
    region = models.CharField(
        max_length=40, blank=True, null=True, db_index=True)
    browser = models.CharField(
        max_length=32, blank=True, null=True, db_index=True)
    os = models.CharField(max_length=32, verbose_name=_(
        'OS'), blank=True, null=True, db_index=True)
    device = models.CharField(
        max_length=32, blank=True, null=True, db_index=True)
    device_type = models.IntegerField(
        choices=DEVICE_TYPE.choices, default=DEVICE_TYPE.UNKNOWN, db_index=True)
    referer = models.CharField(max_length=256, blank=True, null=True)

    user = models.ForeignKey(User, related_name='stats',
                             on_delete=models.CASCADE, default=get_user, blank=True)
    link = models.ForeignKey(Link, related_name='stats',
                             on_delete=models.CASCADE)

    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('-created',)

    def icon(self):
        return "<i class='fa fa-chart-simple nav-icon'></i>"

    @admin.display(description=_('country'))
    def country_flag(self):
        return mark_safe(f"<img src='{self.country.flag}' title='{self.country.name}' > {self.country.name}")

    def __str__(self):
        return self.ip
